const path = require('path');
const fs = require('fs');
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require('@babel/core')
const { SyncHook } = require("tapable"); // 发布订阅

class Compiler {
    static ID = 0;
    constructor(config) {
        this.config = config;
        this.entry = config.entry;
        // 当前目录
        this.root = process.cwd();
        this.modules = {}; // 保存所有模块
        this.ID = 0;

        this.hooks = {
            entryOption: new SyncHook(), // 开始
            compile: new SyncHook(), // 编译
            afterCompile: new SyncHook(['content']), // 编译后
            run: new SyncHook(), // 运行
            emit: new SyncHook(), // 完成
            done: new SyncHook()
        }
        let plugins = this.config.plugins;
        if (Array.isArray(plugins)) {
            plugins.forEach(p => {
                p.apply(this);
            })
        }
    }

    /**
     * 读取文件内容，也就是源代码
     * @param {*} modulePath 
     */
    getSource(modulePath) {
        // for loader
        let rules = this.config.module.rules;
        let content = fs.readFileSync(modulePath, "utf8");
        for (let i = 0; i < rules.length; i++) {
            let rule = rules[i];
            let { test, use } = rule;
            let len = use.length - 1; // loader的总长度
            // 判断是不是less
            if (test.test(modulePath)) {
                function normalLoader() {
                    // 是less
                    let loader = require(use[len--]);
                    content = loader(content);
                    console.log(content);
                    if (len >= 0) {
                        normalLoader();
                    }
                }
                normalLoader();
            }
        }
        return content;
    }

    extName(filename) {
        return filename + (path.extname(filename) ? "" : ".js")
    }

    createAsset(filename, parentPath) {
        let dependencies = [];
        let source = this.getSource(filename);
        const es5str = parser.parse(source, {
            sourceType: "module"
        });
        traverse(es5str, {
            ImportDeclaration: ({ node }) => {
                let moduleName = node.source.value;
                moduleName = this.extName(moduleName);//(path.extname(moduleName) ? "" : ".js");
                moduleName = "./" + path.join(parentPath, moduleName);
                node.source.value = moduleName;
                dependencies.push(moduleName);//this.extName(node.source.value));
            }
        });
        const code = babel.transformFromAstSync(es5str, null, {
            presets: ['@babel/preset-env'],
            plugins: []
        }).code;

        let id = Compiler.ID++;
        return {
            filename,
            code,
            dependencies,
            id
        }
    }

    createGraph(entry) {
        let moduleName = "./" + path.relative(this.root, entry);
        let parentPath = path.dirname(moduleName);
        // 先解析当前文件，
        const mainAssert = this.createAsset(entry, parentPath);
        const queue = [mainAssert];
        // 使用队列
        for (const asset of queue) {
            asset.mapping = {};
            asset.dependencies.forEach(relativePath => {
                const absolutePath = path.resolve(this.root, relativePath); //path.join(__dirname, relativePath);
                const child = this.createAsset(absolutePath, parentPath);
                asset.mapping[relativePath] = child.id;
                queue.push(child);
            })
        }
        return queue;
    }

    outputLastFile(result) {
        let main = path.join(this.config.output.path, this.config.output.filename);
        this.assets = {};
        this.assets[main] = result;
        fs.writeFileSync(main, this.assets[main]);
    }

    boundle(graph) {
        let modules = "";
        graph.forEach(md=>{
            modules+=`
            ${md.id}:[
                function(require,module,exports) {
                    ${md.code}
                },
                ${JSON.stringify(md.mapping)}
            ],`;
        })
        const result = `
            (function(modules){
                function require(id) {
                    const [fn, mapping] = modules[id];
                    function localRequire(relativePath) {
                        return require(mapping[relativePath]);
                    }
                    const module = {
                        exports: {}
                    }
                    fn(localRequire, module, module.exports);
                    return modules.exports;
                }
                require(0);
            })({${modules}})
        `;
        return result;
    }

    // 执行
    run() {
        this.hooks.run.call(); // 消费、
        this.hooks.compile.call(); // 编译
        const graph = this.createGraph(path.resolve(this.root, this.entry));
        const result = this.boundle(graph);
        this.hooks.afterCompile.call(result); // 编译完成
        this.hooks.emit.call();
        this.outputLastFile(result);
        this.hooks.done.call();
    }
}

module.exports = Compiler;